﻿#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

//联合体类型
//联合体类型的声明
    //像结构体一样，联合体也是由一个或者多个成员构成，这些成员可以不同的类型。

//联合类型的声明 
//union Un
//{
//	char c;
//	int i;
//};
//
//int main()
//{
//	//联合变量的定义 
//	union Un un = { 0 };
//	//计算这个变量的大小
//	printf("%zd\n", sizeof(un));//4
//	return 0;
//}

//联合体的特点
    //在联合体中，编译器只为最大的成员分配足够的内存空间。
    //联合体的特点是所有成员共用同一块内存空间。所以联合体也叫：共用体。
//union Un
//{
//    char c;
//    int i;
//};
//int main()
//{
//    union Un un = { 0 };
//    printf("%p\n", &(un.i)); //001AF85C
//    printf("%p\n", &(un.c)); //001AF85C
//    printf("%p\n", &un);     //001AF85C
//    return 0;
//}
    //由输出结果可知联合体内成员的确是在同一块空间

//union Un
//{
//    char c;
//    int i;
//};
//int main()
//{
//    union Un un = { 0 };
//    un.i = 0x11223344;
//    un.c = 0x55;
//    printf("%x\n", un.i); //11223355
//    return 0;
//}
    //由输出结果可知，给联合体其中一个成员赋值，其他成员的值也跟着变化。变相的也说明了所有成员共用同一块空间
    //i在内存中存储形式为44 33 22 11, 44为低地址，大小为1个字节，而c大小也为1个字节，也要放在低地址处
    //又因为成员是共用一块空间，所以两者所在地址是同一个地址
    //所以对c赋值时，i的值也会发生变化

    //一个联合变量的大小，至少是最大成员的大小（因为联合至少得有能力保存最大的那个成员）。
    //但是，联合体大小完全只是最大成员的大小吗？
//union Un
//{
//    char c[5];
//    int i;
//};
//
//int main()
//{
//    union Un u = { 0 };
//    printf("%zd\n", sizeof(u));  //8
//    return 0;
//}
    //由结果可知，并不是我们简单的以为大小就是最大成员char c[5]的大小为5，而是8
    //这就说明一个联合体的大小不完全就是最大成员的大小，但至少是最大成员的大小

//联合体大小的计算
    //这就和之前我们学到的结构体有相似的地方：
    //当最大成员大小不是最大对⻬数的整数倍的时候，就要对齐到最大对齐数的整数倍。
    //对于上面的代码。char c[5]成员相当于放入5个char类型数据，char大小为1，默认对齐数为8，所以 c 的对齐数为1
    // i 的对齐数为4，所以联合体 u 的大小就要对齐到成员最大对齐数4的整数倍
    //而5不是4的整数，所以要浪费3个字节空间大小，故联合体 u 的大小为8

//联合的一个练习
    //在之前我们写了一个代码证明当前机器是大端还是小端，用的方法是指针和强制类型转换
    //现在我们学习了联合体在这个代码中就能发挥出它的作用：
    //首先我们再回顾一下用指针的方法证明：

//int check_sys()
//{
//    int n = 1; //1 在内存中存储形式为01 00 00 00
//    return *(char*)&n;
//}

    //如果不用强制类型转换能解决问题吗？
    //联合体的方法：
//int check_sys()
//{
//    union Un
//    {
//        char c;
//        int i;
//    }u;
//    u.i = 1;       //能用联合体解决这个问题的本质就在于联合体自身的特性：所有成员共用同一块内存空间
//    return u.c;    //所以在给成员 i 赋值时，c也会间接被赋值，但这里巧妙就巧妙在同样的地址，
//}                  //访问 i 会访问4个字节，但访问 c 就只能访问1个字节，用联合体的两个成员就解决了强制类型转换的要求
//
//int main()
//{
//    int ret = check_sys();
//    if (ret == 1)
//    {
//        printf("小端");
//    }
//    else printf("大端");
//    return 0;
//}

//枚举类型
    //枚举顾名思义就是一一列举，把可能的取值一一列举出来

//枚举类型的声明
//enum Color //颜色
//{
//    //三原色的可能取值
//    RED,   //0
//    GREEN, //1
//    BLUE   //2
//};
//    //这些可能取值都是有值的，默认从0开始，依次递增1，当然在声明枚举类型的时候也可以赋初值
//
//enum Sex //性别
//{
//    MALE,      //0
//    FEMALE = 5,//5
//    SECRET     //6
//};
//    //虽然在C语言中可以拿整数给枚举变量赋值，但在C++中是不行的，C++的类型检查比较严格
//    //意思就是说枚举就是不同于 int 类型的一个类型，不能直接赋值
//
//    //以上定义的 enum Sex ， enum Color 都是枚举类型
//
//int main()
//{
//    //RED = 5; //err:表达式必须是可修改的左值
//    //原因就是 {} 中的内容是枚举类型的可能取值，也叫“枚举常量”，而枚举常量一旦初始化，后续就不能进行修改
//    printf("%d\n", RED);   //0
//    printf("%d\n", GREEN); //1
//    printf("%d\n", BLUE);  //2
//    printf("%d\n", MALE);   //0
//    printf("%d\n", FEMALE); //5
//    printf("%d\n", SECRET);  //6
//    return 0;
//}

//枚举类型的优点
    //我们可以使用 #define 定义常量，为什么非要使用枚举？
    //枚举的优点：
    //1. 增加代码的可读性和可维护性
    //2. 和 #define 定义的标识符相比，枚举有类型检查，更加严谨
    //3. 便于调试，预处理阶段会删除 #define 定义的符号
    //4. 使用方便，一次可以定义多个常量
    //5. 枚举常量是遵循作用域规则的，枚举声明在函数内，只能在函数内使用